﻿using System;
using System.Linq;
using Stimulus360.Models.Data;
using System.Collections.Generic;
using Stimulus360.Models.Data.CustomTypes;
using Stimulus360.Repositories.Interfaces;

namespace Stimulus360.Repositories
{
    /// <summary>
    ///     This class is responsible for CRUD operations related to the idea entity.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification="DataContext does not hold expensive resources. Will be garbage collected automatically.")]
    public partial class DataRepository :  IDataRepository
    {
        /// <summary>
        ///     The Linq to SQL DataContext to interact with the Database.
        /// </summary>
        private readonly Stimulus360DataContext _dataContext = new Stimulus360DataContext();

        #region IIdeaRepository Members

        /// <summary>
        ///     Method to get all the ideas in the system in descending order of date submitted.
        /// </summary>
        /// <returns>
        ///     All Ideas in descending order of date submitted
        /// </returns>
        public IQueryable<Idea> GetAllIdeas()
        {
            return from idea in _dataContext.Ideas
                   orderby idea.DateSubmitted descending
                   select idea;
        }

        /// <summary>
        ///     Method to get the new ideas in the system in descending order of date submitted.
        /// </summary>
        /// <returns>
        ///     New ideas in the system in descending order of date submitted.
        /// </returns>
        /// <remarks>
        ///     Current logic returns ideas submitted over the past 30 days(1 month)
        /// </remarks>
        public IQueryable<Idea> GetNewIdeas()
        {
            DateTime viewDate = DateTime.Now.Subtract(new TimeSpan(CalendarYear.ThirtyDays , 0, 0, 0));
            return GetIdeasCreatedOnAfter(viewDate);
        }

        /// <summary>
        ///     Method to get all ideas in a given category in descending order of date submitted.
        /// </summary>
        /// <param name="category">
        ///     Category title of the Category
        /// </param>
        /// <returns>
        ///     All ideas in the given category in descending order of date submitted.
        /// </returns>
        public IQueryable<Idea> FindIdeasInCategory(string category)
        {
            return from idea in _dataContext.Ideas
                   where idea.InvestmentCategory.Category == category
                   orderby idea.DateSubmitted descending
                   select idea;
        }

        /// <summary>
        ///     Method to get all ideas submitted on or after the given date
        ///     in descending order of date submitted.
        /// </summary>
        /// <param name="date">
        ///     The date to be considered.
        /// </param>
        /// <returns>
        ///     All ideas submitted on or after the given date
        ///     in descending order of date submitted.
        /// </returns>
        private IQueryable<Idea> GetIdeasCreatedOnAfter(DateTime date)
        {
            return from idea in _dataContext.Ideas
                   where idea.DateSubmitted >= date && idea.DateSubmitted <=DateTime.Now 
                   orderby idea.DateSubmitted descending
                   select idea;
        }

        /// <summary>
        ///     Method to get the top N ideas ordered by Average rating
        /// </summary>
        /// <param name="topN">
        ///     Number of ideas to be returned
        /// </param>
        /// <returns>
        ///     Top "N" ideas ordered by Average rating
        /// </returns>
        public IQueryable<Idea> GetTopNIdeas(int topN)
        {
            return (from idea in _dataContext.Ideas
                    orderby idea.AverageRating descending
                    select idea).Take(topN);

        }

        /// <summary>
        ///     Method to get all ideas of the given status ordered by date submitted
        /// </summary>
        /// <param name="status">
        ///     The Status to be considered
        /// </param>
        /// <returns>
        ///    All ideas of the given status ordered by date submitted
        /// </returns>
        public IQueryable<Idea> FindIdeasByStatus(IdeaStatus status)
        {
            return from idea in _dataContext.Ideas
                   where idea.Status == (byte)status
                   orderby idea.DateSubmitted descending
                   select idea;
        }

        /// <summary>
        ///     Method to save a new Idea
        /// </summary>
        /// <param name="idea">
        ///     Idea to be created.
        /// </param>
        public void CreateIdea(Idea idea)
        {
            _dataContext.Ideas.InsertOnSubmit(idea);
            Save();
        }

        /// <summary>
        ///     Method to return the Idea for the given idea ID
        /// </summary>
        /// <param name="ideaId">
        ///     The Idea ID for which Idea has to be returned.
        /// </param>
        /// <returns>
        ///     Idea for the given idea ID
        /// </returns>
        public Idea FindIdeaById(int ideaId)
        {
            return _dataContext.Ideas.FirstOrDefault(i => i.IdeaId == ideaId);
        }

        /// <summary>
        ///     Method to updated an edited idea to the database
        /// </summary>
        public void UpdateIdea()
        {
            Save();
        }

        /// <summary>
        ///     Method to get all feedback for the given idea ordered by date submitted
        /// </summary>
        /// <param name="ideaId">
        ///     Idea ID of the idea for which feedback is to be retrieved
        /// </param>
        /// <returns>
        ///     All feedback for the given idea ordered by date submitted
        /// </returns>
        public IQueryable<IdeaRating> GetIdeaFeedback(int ideaId)
        {
            return from feedback in _dataContext.IdeaRatings
                   where feedback.IdeaId == ideaId
                   orderby feedback.DateSubmitted descending
                   select feedback;
        }

        /// <summary>
        ///     Method to save the tracked DataContext changes to the DB.
        /// </summary>
        private void Save()
        {
            _dataContext.SubmitChanges();
        }

        /// <summary>
        ///     This method is used to get the User's feedback for a specific Idea
        /// </summary>
        /// <param name="ideaId">
        ///     IdeaID for which feedback is to be retrieved.
        /// </param>
        /// <param name="userName">
        ///     User whose feedback is to be retrieved
        /// </param>
        /// <returns>
        ///     User Feedback for the given idea.
        /// </returns>
        public IdeaRating GetIdeaFeedback(int ideaId, string userName)
        {
            return _dataContext.IdeaRatings.SingleOrDefault(r => ((r.UserName == userName) && r.IdeaId == ideaId));
        }

        /// <summary>
        ///     Method to get list of projects associated with the idea
        /// </summary>
        /// <param name="ideaId">
        ///     IdeaID for which list of projects is to be retrieved.
        /// </param>
        /// <returns>
        ///     List of associated projects
        /// </returns>
        public ICollection<ProjectSummary> GetProjectsForIdea(int ideaId)
        {
            return (from project in _dataContext.ProjectIdeas
                    where project.IdeaId == ideaId
                    select project.Project).ProjectSummary();

        }

     

        /// <summary>
        ///     Method to retrieve list of ideas satisfying the search criteria
        /// </summary>
        /// <param name="searchText">
        ///     The search criteria
        /// </param>
        /// <returns>
        ///     The list of ideas satisfying the search criteria
        /// </returns>
        public IQueryable<Idea> FindIdeasBySearchString(string searchText)
        {
            return from idea in _dataContext.Ideas
                   where (idea.Title.ToLower().Contains(searchText.ToLower())) || (idea.InvestmentCategory.Category.ToLower() == searchText.ToLower())
                   orderby idea.DateSubmitted descending
                   select idea;
        }

        /// <summary>
        ///     Method to get the list of ideas submitted by the given user
        /// </summary>
        /// <param name="userName">
        ///     User whose submitted idea list is to be retrieved
        /// </param>
        /// <returns>
        ///     List of ideas submitted by the given user.
        /// </returns>
        public IQueryable<Idea> GetSubmittedIdeas(string userName)
        {
            return from idea in _dataContext.Ideas
                   where idea.SubmittedBy == userName
                   orderby idea.DateSubmitted descending
                   select idea;

        }

        /// <summary>
        ///     Method to get the users favorite ideas
        /// </summary>
        /// <param name="userName">
        ///     User whose favorite idea list is to be retrieved
        /// </param>
        /// <returns>
        ///     The list of users favorite ideas.
        /// </returns>
        public IQueryable<Idea> GetFavoriteIdeas(string userName)
        {
            //Since we want a IQuerable we cannot extract rating.Idea from this query. To get
            // an IQueryable the query must exist on the entity itself

            var favoriteIds = from rating in _dataContext.IdeaRatings
                              where rating.UserName == userName && rating.Favorite == true
                              select rating.IdeaId;

            return from idea in _dataContext.Ideas
                   where favoriteIds.Contains(idea.IdeaId)
                   select idea;

        }

        /// <summary>
        ///     Method to get a list of recommended ideas based on the users interests
        /// </summary>
        /// <param name="userName">
        ///     User for whom recommendations have to be made
        /// </param>
        /// <returns>
        ///     List of recommended ideas.
        /// </returns>
        public IQueryable<Idea> GetRecommendedIdeas(string userName)
        {
            //All ideas of User's interest submitted over last 30 days which the user has not rated.
            DateTime viewDate = DateTime.Now.Subtract(new TimeSpan(CalendarYear.ThirtyDays , 0, 0, 0));

            var latestIdeas = GetIdeasCreatedOnAfter(viewDate);
            var userRatedIdeas = from ratings in _dataContext.IdeaRatings
                                 where ratings.UserName == userName
                                 select ratings.Idea ;
            var userInterestIds = from userInterest in _dataContext.UserInterests
                                  where userInterest.UserName == userName
                                  select userInterest.CategoryId;

            var nonRatedLatestIdeas = latestIdeas.Except(userRatedIdeas);

            return from idea in nonRatedLatestIdeas 
                   where ( userInterestIds.Contains(idea.InvestmentCategory.CategoryId))
                   orderby idea.DateSubmitted
                   select idea;


        }
        #endregion
    }
}
